/*
从avalon上扒下来，修修改改的加载器
author: chendye
QQ: 1025635227
Email: 1025635227@qq.com
update:

2017.03.02:
version 2.0.0

1. 面向对象定义模块
2. 按jsdoc规范更新注释
3. 调整部分命名
*/
;;
new function() {
	"use strict";
	/**
	 * 路径处理对像
	 */
	var pathUtil = {
		/**
		 * 通过错误栈，获取当前脚本的完整地址
		 * inspireb by https://github.com/samyk/jiagra/blob/master/jiagra.js
		 * @param {Boolean} base 是否是获取基本路径
		 * @return {String} 
		 * */
		getCurrentScript: function(base) {
			var stack
			try {
				a.b.c() //强制报错,以便捕获e.stack
			} catch(e) { //safari的错误对象只有line,sourceId,sourceURL
				stack = e.stack
				if(!stack && window.opera) {
					stack = (String(e).match(/of linked script \S+/g) || []).join(" ")
				}
			}
			if(stack) {
				stack = stack.split(/[@ ]/g).pop() //取得最后一行,最后一个空格或@之后的部分
				stack = stack[0] === "(" ? stack.slice(1, -1) : stack.replace(/\s/, "") //去掉换行符
				return stack.replace(/(:\d+)?:\d+$/i, "") //去掉行号与或许存在的出错字符起始位置
			} else {
				console.error("获取脚本地址失败！")
			}
		},
		/**
		 * 去掉url地址中的hash值和查询条件
		 * @param {URL} url
		 * @return {URL}
		 * */
		trimHashAndQuery: function(url) {
			return(url || "").replace(/[?#].*/, "")
		},
		/**
		 * 是否绝对路径
		 * */
		isAbsUrl: function(path) {
			return /^(?:[a-z]+:)?\/\//i.test(String(path))
		},
		/**
		 * 将相对路径转换成绝对路径
		 * @param {URL} base 参照路径
		 * @param {URL} rel 相对路径
		 * */
		joinPath: function(base, rel) {
			var rdeuce = /\/\w+\/\.\./
			//末尾补/
			if(base.charAt(base.length - 1) !== "/") {
				base += "/"
			}

			//相对于兄弟路径
			if(rel.slice(0, 2) === "./") {
				return base + rel.slice(2)
			}
			if(rel.slice(0, 1) === "/") {
				return base + rel.slice(1)
			}

			//相对于父路径
			if(rel.slice(0, 2) === "..") {
				base += rel // example: d/a/../b/c
				while(rdeuce.test(base)) {
					base = base.replace(rdeuce, "")
				}
				return base
			}
			return base + rel
		}
	};
	//loader.js文件所在目录
	pathUtil.currentPath = (function() {
		var url = pathUtil.getCurrentScript(true);
		if(!url) { //处理window safari的Error没有stack的问题
			url = DOC.scripts[DOC.scripts.length - 1].src;
		}
		url = pathUtil.trimHashAndQuery(url);
		return url.slice(0, url.lastIndexOf("/") + 1);
	}());

	/**
	 * 获取参照路径 
	 * @param {URL} parentUrl
	 * @return {URL}
	 * */
	function getBaseUrl(parentUrl) {
		return require.baseUrl ? require.baseUrl :
			parentUrl ? parentUrl.substr(0, parentUrl.lastIndexOf("/")) :
			pathUtil.currentPath
	};

	/**
	 * 获取ajax对象
	 * */
	function getXHR() {
		return new(window.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP")
	};

	var noop = function() {};

	/**
	 * 模块的构造函数
	 * @param {String} id 
	 * @param {Function} factory
	 * @param {Array} deps
	 */
	function Module(id, factory, deps) {
		this.id = id;
		this.state = 0;
		this.factory = factory || noop;
		this.deps = deps || [];

		var p = id.slice(id.lastIndexOf(".") + 1, id.length);
		this.loader = loaders[p] || loaders.text;

		this.holder = null;
	};

	/**
	 * 改变状态
	 * @param {Number} state = [0|1|2|4|-1] 0初始化，1加工完成，2加载完成(js)，4正在加载，-1加载失败
	 */
	Module.prototype.changeState = function(state) {
		this.state = state;
	};
	/**
	 * 加载文件
	 */
	Module.prototype.load = function(reload) {
		if (this.factory.shim) {//依赖加载完，才能加载自身
			return;
		};
		if(reload||this.state < 1) {
			this.holder = this.loader(this.id,reload);
			this.changeState(4);
			return this;
		};
	};
	/**
	 * 加工
	 */
	Module.prototype.fireFactory = function() {
		var _this=this;
		var deps = _this.deps,
			factory = _this.factory;

		for(var i = 0, array = [], d; d = deps[i++];) {
			modules[d].exports && array.push(modules[d].exports)
		}
		try{
			var ret = factory.apply(window, array)
		}catch(e){
			console.error(e);
			if(!require.debug){
				//生产环境导致执行出错，考虑dns原因，资源的依赖项需要重新加载一次
				deps.forEach(function(d){
					if(!modules[d]){
						modules[d].load(true);
					}
				});
				_this.changeState(2);
				return false;
			};
			return true;
		}
		if(ret !== void 0) {
			_this.exports = ret
		}
		_this.changeState(1);
		return true;
	};

	var loaders = {
		/**
		 * 加载js
		 * */
		js: function(url,reload) {
			var node = DOC.createElement("script");
			node.src = getStamp(url,reload);
			require.debug && console.log("debug: 开始加载 " + url)
			return insertNode(node);
		},
		/**
		 * 加载css
		 * */
		css: function(url,reload) {
			var node = DOC.createElement("link");
			node.rel = "stylesheet";
			node.href = getStamp(url,reload);
			require.debug && console.log("debug: 开始加载 " + url)
			return insertNode(node);
		},
		/**
		 * 加载其他资源
		 */
		text: function(url,reload) {
			var xhr = getXHR()
			xhr.onreadystatechange = function() {
				if(xhr.readyState === 4) {
					var status = xhr.status;
					var module = modules[url];
					if((status > 399 && status < 600) || status === 0) {
						module.changeState(-1);
						console.error("debug: 加载 " + (url) + " 失败。" + status)
					} else {
						module.exports = require.onText ? require.onText.call(null, xhr.responseText) : xhr.responseText;
						module.changeState(1);
						require.debug && console.log("debug: 加载完成 " + url)
					}
					module.holder = null;
				}
			};
			xhr.open("GET", getStamp(url,reload), true)
			if("withCredentials" in xhr) {
				xhr.withCredentials = true
			}
			xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
			xhr.send();
			require.debug && console.log("debug: 开始加载 " + url)
			return xhr;
		}
	}

	function getStamp(url,reload) {
		if(require.nocache||reload){
			return url + "?_" + new Date().getTime();
		};
		if(require.loadStamp){
			return url + "?_" + require.loadStamp;
		};
		return url;
	}

	var DOC = window.document,
		HEAD = DOC.getElementsByTagName("head")[0],
		W3C = window.dispatchEvent;

	//模块的登记处
	var modules = {};
	//		loaders = {},//加载器登记处

	var requires = [], //加载块
		startTime_check, //开始加载的时间
		id_check = 0, //0表示未开始，其他值表示轮询的settimeout值
		failedCount_load = 0, //0表示没有，1+表示有
		reload = 0; //0表示不允许，1表示允许；

	/**
	 * amd的 require函数，加载功能模块
	 * @param {Array} arr 模块路径的数组
	 * @param {Function} factory 对模块进行加工的函数
	 * @param {URL} parentUrl 加载的相对路径，用做mid，一般不传
	 * */
	window.require = function(arr, factory, parentUrl,id) {

		//模块的id
		var mid = id||parentUrl || ("callback" + setTimeout("1"));
		require.urlCaseInsensitive && (mid = mid.toLowerCase());

		if(!parentUrl) {
			requires.push(mid);
		}
		//相对路径
		parentUrl = getBaseUrl(parentUrl)

		//解析加载项
		var deps = [];
		arr.forEach(function(dep) {
			var depargs = getDepArgs(dep, parentUrl);
			deps.push(depargs[2]);
			require.apply(null, depargs);
			modules[depargs[2]].load();
		});
		
		var curm=modules[mid];
		//注册
		if(!curm) {
			modules[mid] = new Module(mid, factory, deps);
		}
		if(curm&&curm.redefined){
			curm.redefined=false;
			curm.deps=deps;
			curm.factory=factory||noop;
		}

		//开始检查
		if(!id_check) {
			beginCheck();
		}
	};

	/**
	 * amd的 define函数，定义功能模块
	 * @param {URL} urlOrId
	 * @param {Array} deps
	 * @param {Function} factory
	 */
	window.define = function(deps, factory) {
	
		var args = Array.prototype.slice.call(arguments);
		var id,url;
		if(typeof args[0]==="string"){
			var id=args.shift();
		};

		var url = pathUtil.trimHashAndQuery(pathUtil.getCurrentScript());

		if(typeof args[0] === "function") {
			args.unshift([]);
		};
		id||(id=url);
		modules[id]&&(modules[id].redefined=true);
		args.push(url);
		id!==url&&(args.push(id));
		require.apply(null, args);

	};
	require.amd = define.amd = true;

	var defaults = {
		waitSeconds: 20,
		baseUrl: "",
		paths: {},
		shim: {},
		loadStamp: "",
		nocache: false,
		debug: true,
		urlCaseInsensitive: false,

		onStart: null,
		onComplete: null,
		onError: null,
		onText: null
	};
	require.config = function(settings) {
		for(var p in settings) {
			if(defaults.hasOwnProperty(p)) {
				require[p] = settings[p];
			}
		};
	};
	//应用默认配置
	require.config(defaults);

	/**
	 * 解析出dep的地址、依赖、shim配置
	 * @param {URL} url
	 * @param {URL} parentUrl
	 * @return {Array} 返回需要实例一个模块的参数
	 */
	function getDepArgs(url, parentUrl) {

		var ext = ".js",
			shim = require.shim[url],
			deps = [],
			factory = noop;
		//1. 扩展名
		url = url.replace(/^(\w+)!/, function(a, b) {
			ext = "." + b;
			return ""
		});
		//2. 绝对地址
		//解析paths
		url = url.split("/");
		for(var i = url.length, parentModule, parentPath; i > 0; i -= 1) {
			parentModule = url.slice(0, i).join('/');
			parentPath = require.paths[parentModule];
			if(parentPath) {
				url.splice(0, i, parentPath); // 替换配置
				break;
			};
		};
		url = url.join('/');

		//补全后缀
		if(ext !== ".text" && url.slice(0 - ext.length) !== ext) {
			url += ext
		};

		//转换为绝对路径
		if(!pathUtil.isAbsUrl(url)) {
			url = pathUtil.joinPath(parentUrl, url);
		};

		//先加载依赖，再加载主文件
		if(shim) {
			//3. 依赖项
			shim.deps&&(deps = shim.deps);
			//4. 工厂函数
			factory = function() {
				if(shim.export){
					return eval(shim.exports);
				};
			};
			factory.shim=true;
		};

		return [deps, factory, url];
	}

	function insertNode(node) {
		//	console.log("开始加载："+node.src||node.href)
		node[W3C ? "onload" : "onreadystatechange"] = function() { //先解析，再执行该回调
			if(W3C || /loaded|complete/i.test(node.readyState)) {
				//处理加载结果
				checkFail(node, false, !W3C)
			}
		};
		node.onerror = function() {
			checkFail(node, true);
		};
		HEAD.insertBefore(node, HEAD.firstChild);

		return node;
	}

	function checkFail(node, iserror, fuckIE) {
		var id = pathUtil.trimHashAndQuery(node.src || node.href) //检测是否死链
		var module = modules[id]

		node.onload = node.onreadystatechange = node.onerror = null
		module.holder = null;

		if(iserror) {
			setTimeout(function() {
				HEAD.removeChild(node) //移除掉
				node = null // 处理旧式IE下的循环引用问题
			})
			module.changeState(-1);
			console.error("debug: 加载失败" + id)
		} else {
			require.debug && console.log("debug: 加载完成 " + (node.src || node.href));
			module.changeState(node.src ? 2 : 1);
			return true
		}
	}

	function checkLoaded(isFirst) {
		console.log("lkai")
		//是否超时
		//		var expire=false;
		var expire = (new Date().getTime() - startTime_check) >= require.waitSeconds * 1000;
		//检查
		for(var i = requires.length, m; i >= 0, m = requires[--i];) {

			var module = modules[m]
			if(checkDeps(module.deps, m, expire,isFirst)) {
				//枝干拼好了，种树
				if(module.fireFactory()){
					requires.splice(i, 1);
				};
			}
		}

		//判断检查结果
		if(requires.length && !failedCount_load) {

			id_check = setTimeout(checkLoaded, 50);

		} else if(failedCount_load) {
			//			console.error("检查出错。")
			clearTimeout(id_check);
			try {
				require.onComplete && require.onComplete();
				require.onError ? require.onError.apply(null, [beginCheck, checkComplete]) : checkComplete();
			} catch(e) {
				console.error(e);
			}
		} else {
			//			console.info("检查完成")
			checkComplete(true);
			try {
				require.onComplete && require.onComplete();
			} catch(e) {
				console.error(e);
			}
		}
	}

	function beginCheck() {
		failedCount_load = 0;
		startTime_check = new Date().getTime();
		//		console.log(requires);
		try {
			require.onStart && require.onStart();
		} catch(e) {
			console.error(e)
		}
		id_check = setTimeout(function(){
			checkLoaded.apply(null,[true]);
		});
	}

	function checkComplete(isSuccess) {
		requires.length = 0;
		id_check = 0;
		//清理临时root
		for(var i in modules) {
			if(modules.hasOwnProperty(i) && /^callback(?:\d+)/.test(i)) {
				//				console.log(modules[i]);
				isSuccess && require.router && require.router.checkState(modules[i]);
				delete modules[i];
			}
		}
	}

	function checkDeps(deps, m, expire,isFirst) {

		var suc = 0;
		for(var i = 0, j = deps.length; i < j; i++) {

			var mm = deps[i];

			var module = modules[mm];

			switch(module.state) {
				case 0:
					if(module.factory.shim&&checkDeps(module.deps, mm, expire,isFirst)){
						module.factory.shim=false;
						module.load();
					}
					break;
				case 4:
					if(expire&&module.holder) {
						//超时了
						module.holder.abort ? module.holder.abort() : checkFail(module.holder, true);
						console.info("文件加载超时：" + module.id);
					}
					break;
				case 2:
					if(checkDeps(module.deps, mm, expire)) {
						module.fireFactory();
					};
					break;
				case -1:
					isFirst?module.load(true):failedCount_load++;
					break;
			}
			module.state === 1 && suc++;
		}
		return suc === deps.length;
	}
}
